package com.gitee.wsl.result

import com.gitee.wsl.result.sample.ValueResult
import com.gitee.wsl.result.sample.ProcessResult
import com.gitee.wsl.result.sample.ValueResult.Companion.Err
import com.gitee.wsl.result.sample.ValueResult.Companion.Ok
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
 * Calls the specified function [block] and returns its encapsulated result if invocation was
 * successful, catching any [Throwable] exception that was thrown from the [block] function
 * execution and encapsulating it as a failure.
 */
@OptIn(ExperimentalContracts::class)
inline fun <V> runCatching(block: () -> V): EntryResult<V, Throwable> {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    return try {
        Ok(block())
    } catch (e: Throwable) {
        Err(e)
    }
}

/**
 * Calls the specified function [block] with [this] value as its receiver and returns its
 * encapsulated result if invocation was successful, catching any [Throwable] exception that was
 * thrown from the [block] function execution and encapsulating it as a failure.
 */
@OptIn(ExperimentalContracts::class)
inline infix fun <T, V> T.runCatching(block: T.() -> V): EntryResult<V, Throwable> {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    return try {
        Ok(block())
    } catch (e: Throwable) {
        Err(e)
    }
}

fun <V,E> V?.toResult(): ValueResult<V, E>? {
    return if(this!=null) ValueResult.Ok(this) else null
}

fun <V,E,S> V?.toStateResult(): StateEntryResult<V, E,S>? {
    return if(this!=null) ProcessResult.Ok(this) else null
}

/**
 * Converts a nullable of type [V] to a [ValueResult]. Returns [Ok] if the value is
 * non-null, otherwise the supplied [error].
 */
@OptIn(ExperimentalContracts::class)
inline infix fun <V, E> V?.toResultOr(error: () -> E): EntryResult<V, E> {
    contract {
        callsInPlace(error, InvocationKind.AT_MOST_ONCE)
    }

    return when (this) {
        null -> Err(error())
        else -> Ok(this)
    }
}

@OptIn(ExperimentalContracts::class)
inline fun <V,S> runCatchingWithState(
    initState:S?,block: () -> V
): StateEntryResult<V, Throwable, S> {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    return try {
        val value = block()
        ProcessResult.Ok(value)
    } catch (e: Throwable) {
        ProcessResult.Err(e)
    }
}


@OptIn(ExperimentalContracts::class)
inline fun <V,S> runCatching(
    initState:S?,
    crossinline block: StateProxyCollector<V,S>.() -> V
): Flow<StateEntryResult<V, Throwable, S>> {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    return flow {
        initState?.let { emit(ProcessResult.StateValue(it))}

        val stateProxyCollector = object:StateProxyCollector<V,S>{
            override suspend fun emit(value: V) {
                emit(ProcessResult.Ok(value))
            }

            override suspend fun emitState(state: S) {
                emit(ProcessResult.StateValue(state))
            }
        }

        try {
            val value = block(stateProxyCollector)
            emit(ProcessResult.Ok(value))
        } catch (e: Throwable) {
            emit(ProcessResult.Err(e))
        }
    }
}

interface StateProxyCollector<T,S> : FlowCollector<T> {

    suspend fun emitState(state:S)

    /*suspend fun collectTo(scope: ProducerScope<T>) {
        channel.consumeEach { elem ->
            scope.send(elem)
        }
    }

    fun close() {
        channel.close()
    }*/
}